package cn.coder.toolkit;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.TypeVariable;
import java.util.List;

/**
 * 反射
 *
 * @author mrathena
 */
public final class ReflectionKit {

    /**
     * 反射基础知识
     */
    public static class BaseDemo {
        public static void main(String[] args) {

            try {

                System.out.println("基本数据类型");
                System.out.println(byte.class); // byte
                System.out.println(short.class); // short
                System.out.println(int.class); // int
                System.out.println(long.class); // long
                System.out.println(float.class); // float
                System.out.println(double.class); // double
                System.out.println(boolean.class); // boolean
                System.out.println(char.class); // char
                System.out.println();
                System.out.println("基本数据类型 2");
                System.out.println(Byte.TYPE); // byte
                System.out.println(Short.TYPE); // short
                System.out.println(Integer.TYPE); // int
                System.out.println(Long.TYPE); // long
                System.out.println(Float.TYPE); // float
                System.out.println(Double.TYPE); // double
                System.out.println(Boolean.TYPE); // boolean
                System.out.println(Character.TYPE); // char
                System.out.println();
                System.out.println("包装数据类型");
                System.out.println(Byte.class); // java.lang.Byte
                System.out.println(Short.class); // java.lang.Short
                System.out.println(Integer.class); // java.lang.Integer
                System.out.println(Long.class); // java.lang.Long
                System.out.println(Float.class); // java.lang.Float
                System.out.println(Double.class); // java.lang.Double
                System.out.println(Boolean.class); // java.lang.Boolean
                System.out.println(Character.class); // java.lang.Character
                System.out.println();
                System.out.println("基本数据类型一维数组");
                System.out.println(byte[].class); // [B
                System.out.println(short[].class); // [S
                System.out.println(int[].class); // [I
                System.out.println(long[].class); // [J
                System.out.println(float[].class); // [F
                System.out.println(double[].class); // [D
                System.out.println(boolean[].class); // [Z
                System.out.println(char[].class); // [C
                System.out.println();
                System.out.println("包装数据类型一维数组: 用 [L 开头, 用 ; 结尾, 即代表一维数组类型, 用 String 举例, [Ljava.lang.String;");
                System.out.println(Byte[].class); // [Ljava.lang.Byte;
                System.out.println(Short[].class); // [Ljava.lang.Short;
                System.out.println(Integer[].class); // [Ljava.lang.Integer;
                System.out.println(Long[].class); // [Ljava.lang.Long;
                System.out.println(Float[].class); // [Ljava.lang.Float;
                System.out.println(Double[].class); // [Ljava.lang.Double;
                System.out.println(Boolean[].class); // [Ljava.lang.Boolean;
                System.out.println(Character[].class); // [Ljava.lang.Character;
                System.out.println();
                System.out.println("二维数组: 在一维数组的前面再加一个 [, 如 [[Ljava.lang.String;");
                System.out.println(byte[][].class); // [[B
                System.out.println(Byte[][].class); // [[Ljava.lang.Byte;
                System.out.println(String[][].class); // [[Ljava.lang.String;
                System.out.println();
                System.out.println("多维数组: N 维数组就是前面 N 个 [ 后面一个 ;");
                System.out.println(String[][][][].class); // [[[[Ljava.lang.String;
                System.out.println();
                System.out.println("通过 字符串 找 Class");
                System.out.println(Class.forName("[[[[Ljava.lang.String;"));
                System.out.println(Class.forName("[[Ljava.lang.String;"));
                System.out.println(Class.forName("[[B"));
                System.out.println(Class.forName("[B"));
                System.out.println(Class.forName("[Ljava.lang.Byte;"));
                System.out.println("基本数据类型反向找 Class 不太容易, 查看 Byte.TYPE 可知 Class.getPrimitiveClass(\"byte\") 可以实现, 但是该方法是私有的, JDK 模块化后无法直接调用");
                Class<?> clazz = Class.class;
                Method method = clazz.getDeclaredMethod("getPrimitiveClass", String.class);
//                method.setAccessible(true);
//                Class<?> primitiveClass = (Class<?>) method.invoke(null);
//                System.out.println("Primitive Class: " + primitiveClass);

                System.out.println();
                System.out.println("反射类型判断");

                class TypeTest<T> {

                    private int i;
                    private Integer it;
                    private int[] iarray;
                    private List list;
                    private List<String> slist;
                    private List<T> tlist;
                    private T t;
                    private T[] tarray;
                }

                test(TypeTest.class.getDeclaredField("i"));
                test(TypeTest.class.getDeclaredField("it"));
                test(TypeTest.class.getDeclaredField("iarray"));
                test(TypeTest.class.getDeclaredField("list"));
                test(TypeTest.class.getDeclaredField("slist"));
                test(TypeTest.class.getDeclaredField("tlist"));
                test(TypeTest.class.getDeclaredField("t"));
                test(TypeTest.class.getDeclaredField("tarray"));


            } catch (Throwable cause) {
                throw new RuntimeException(cause);
            }
        }

        public static void test(Field field) {
            System.out.println();
            System.out.println(field.getName() + " : " + (field.getType().isPrimitive() ? "是" : "不是") + " 基本数据类型");
            System.out.println(field.getName() + " : " + (field.getType().isArray() ? "是" : "不是") + " 数组");
            System.out.println(field.getName() + " : " + (field.getGenericType() instanceof GenericArrayType ? "是" : "不是") + " 泛型数组");
            System.out.println(field.getName() + " : " + (field.getGenericType() instanceof ParameterizedType ? "是" : "不是") + " 泛型类型");
            System.out.println(field.getName() + " : " + (field.getGenericType() instanceof TypeVariable ? "是" : "不是") + " 泛型变量");
        }
    }

    private ReflectionKit() {}

    /**
     * 通过反射取对象指定字段(属性)的值
     *
     * @param object    目标对象
     * @param fieldName 字段的名字
     * @return 字段的值
     */
    public static Object getValue(Object object, String fieldName) {
        Class<?> clazz = object.getClass();
        String[] fs = fieldName.split("\\.");

        try {
            for (int i = 0; i < fs.length - 1; i++) {
                Field field = clazz.getDeclaredField(fs[i]);
                field.setAccessible(true);
                object = field.get(object);
                clazz = object.getClass();
            }

            Field field = clazz.getDeclaredField(fs[fs.length - 1]);
            field.setAccessible(true);
            return field.get(object);
        } catch (Throwable throwable) {
            throw new RuntimeException(throwable);
        }
    }

    /**
     * 通过反射给对象的指定字段赋值
     *
     * @param object    目标对象
     * @param fieldName 字段的名称
     * @param value     值
     */
    public static void setValue(Object object, String fieldName, Object value) {
        Class<?> clazz = object.getClass();
        String[] fs = fieldName.split("\\.");
        try {
            for (int i = 0; i < fs.length - 1; i++) {
                Field field = clazz.getDeclaredField(fs[i]);
                field.setAccessible(true);
                Object val = field.get(object);
                if (val == null) {
                    Constructor<?> c = field.getType().getDeclaredConstructor();
                    c.setAccessible(true);
                    val = c.newInstance();
                    field.set(object, val);
                }
                object = val;
                clazz = object.getClass();
            }

            Field field = clazz.getDeclaredField(fs[fs.length - 1]);
            field.setAccessible(true);
            field.set(object, value);
        } catch (Throwable throwable) {
            throw new RuntimeException(throwable);
        }
    }

    /**
     * 调用指定方法
     *
     * @param object     目标对象
     * @param methodName 方法名称
     * @param parameters 参数
     * @return .
     */
    public static Object invoke(Object object, String methodName, Object... parameters) {
        Class<?> clazz = object.getClass();
        try {
            Method method;
            if (parameters != null && parameters.length != 0) {
                Class<?>[] parameterTypes = new Class[parameters.length];
                for (int i = 0; i < parameters.length; i++) {
                    parameterTypes[i] = parameters.getClass();
                }
                method = clazz.getMethod(methodName, parameterTypes);
            } else {
                method = clazz.getMethod(methodName);
            }
            method.setAccessible(true);
            return method.invoke(object, parameters);
        } catch (Throwable throwable) {
            throw new RuntimeException(throwable);
        }
    }

    /**
     * 创建新对象
     *
     * @param object     目标对象
     * @param parameters 构造函数参数
     * @return .
     */
    public static Object newInstance(Object object, Object... parameters) {
        Class<?> clazz = object.getClass();
        try {

            if (parameters != null && parameters.length != 0) {
                Class<?>[] parameterTypes = new Class[parameters.length];
                for (int i = 0; i < parameters.length; i++) {
                    parameterTypes[i] = parameters.getClass();
                }
                Constructor<?> constructor = clazz.getDeclaredConstructor(parameterTypes);
                constructor.setAccessible(true);
                return constructor.newInstance(parameters);
            } else {
                try {
                    return clazz.newInstance();
                } catch (Throwable throwable) {
                    Constructor<?> constructor = clazz.getDeclaredConstructor();
                    constructor.setAccessible(true);
                    return constructor.newInstance();
                }
            }
        } catch (Throwable throwable) {
            throw new RuntimeException(throwable);
        }
    }

}